# This file is part of Neuroinfo Toolkit.
#
# Neuroinfo Toolkit is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Neuroinfo Toolkit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Neuroinfo Toolkit.  If not, see <http://www.gnu.org/licenses/>.

from neuro.exceptions import IllegalArgumentException

def union(list1, list2):
	'''
	Create a union of two lists. No redundancy ::
	
		>>> list1 = [1, 2, 3, 4]
		>>> list2 = [3, 4, 5, 6]
		>>> union(list1, list2)
		[1, 2, 3, 4, 5, 6]
		
	:param list1: List 1
	:type list1: list
	:param list2: List 2
	:type list2: list
	:returns: Union of lists
	:rtype: list
	'''
	if(not isinstance(list1, list)):
		raise IllegalArgumentException("List 1 must be an instance of list")
	elif(not isinstance(list2, list)):
		raise IllegalArgumentException("List 2 must be an instance of list")
	
	union = list(set(list1).union(set(list2)))
	
	return union

def merge(array1, array2):
	'''
	Return union of two lists or dicts. Redundancy allowed ::
	
		>>> list1 = [1, 2, 3, 4]
		>>> list2 = [1, 2, 3, 4]
		>>> merge(list1, list2)
		[1, 2, 3, 4, 1, 2, 3, 4]
		
	:param array1: Input 1
	:type array1: list, dict
	:param array2: Input 2
	:type array2: list, dict
	:rtype: list, dict
	'''
	if(isinstance(array1, list)):
		if(not isinstance(array2, list)):
			raise IllegalArgumentException("Both arrays must be of the same type e.g. list")
		type = "list"
	elif(isinstance(array1, dict)):
		if(not isinstance(array2, dict)):
			raise IllegalArgumentException("Both arrays must be of the same type e.g. dict")		
		type = "dict"
	else:
		raise IllegalArgumentException("Arrays must be instances of either list or dict")
	
	if(type == "dict"):
		array1.update(array2)
		return array1
		
	if(type == "list"):
		return array1 + array2
	
def intersect(array1, array2):
	'''
	Return intersection of two lists or dicts. Returns common elements ::
	
		>>> list1 = [1, 2, 3, 4]
		>>> list2 = [3, 4, 5, 6]
		>>> intersect(list1, list2)
		[3, 4]
		
	:param array1: Input 1
	:type array1: list, dict
	:param array2: Input 2
	:type array2: list, dict
	:rtype: list, dict
	'''
	if(isinstance(array1, list)):
		if(not isinstance(array2, list)):
			raise IllegalArgumentException("Both arrays must be of the same type e.g. list")
		type = "list"
	elif(isinstance(array1, dict)):
		if(not isinstance(array2, dict)):
			raise IllegalArgumentException("Both arrays must be of the same type e.g. dict")		
		type = "dict"
	else:
		raise IllegalArgumentException("Arrays must be instances of either list or dict")
	
	if(type == "dict"):
		return array1.intersection_update(array2)
		
	if(type == "list"):
		return array1 + array2

def implode(input, delimiter=" "):
	'''
	Implode list or dict values into a string ::
	
		>>> input = [1, 2, 3, 4]
		>>> implode(input, " @ ")
		'1 @ 2 @ 3 @ 4'
		
	:param input: Input list or dict
	:type input: list, dict
	:param delimiter: Delimiter to join on
	:type delimiter: str
	:rtype: str
	'''
	if(isinstance(input, dict)):
		input = input.values()

	if(not isinstance(input, list)):
		raise IllegalArgumentException("Input to implode must be an instance of list or dict")
	elif(len(input) == 0):
		return ""
	
	if(not isinstance(delimiter, basestring)):
		raise IllegalArgumentException("Implode delimiter must be an instance of str")
	
	string = delimiter.join(["%s" % item for item in input])
		
	return string

def swapDict(dictionary):
	'''
	Swap all keys and values in a dictionary. Dulplicate values will be 
	overwritten ::
	
		>>> input = {"a": 1, "b": 2}
		>>> swapDict(input)
		{1: "a", 2: "b"}

	:param dictionary: Dictionary to swap
	:type dictionary: dict
	:returns: Swapped dictionary
	:rtype: dict
	'''
	if(not isinstance(dictionary, dict)):
		raise IllegalArgumentException("Dictionary must be an instance of dict")
	
	if(len(dictionary) == 0):
		return None
	else:
		return dict([(v, k) for (k, v) in dictionary.iteritems()])
	
def getKey(dictionary, value):
	'''
	Retrieve the first dictionary key that contains a specified value ::

		>>> input = {"a": 1, "b": 2, "c": 1}
		>>> getKey(input)
		'a'

	:param dictionary: Dictionary to search, haystack
	:type dictionary: dict
	:param value: Value to search for, needle
	:type value: int, float, str, ...
	'''
	if(not isinstance(dictionary, dict)):
		raise IllegalArgumentException("Dictionary must be an instance of dict")
	
	if(len(dictionary) == 0):
		return None
	
	if(not value):
		raise IllegalArgumentException("Value cannot be null")
	
	for (key, val) in dictionary.items():
		if(val == value):
			return key
		
	return None

def flatten(struct):
	'''
	Flatten a one-dimensional list or dict and return the single item ::

		>>> input = [1]
		>>> flatten(input)
		1

	:param struct: List to flatten
	:type struct: list, dict
	:returns: Single element
	:rtype: int, float, str, ...
	'''
	if(not isinstance(struct, list) and not isinstance(struct, dict)):
		raise IllegalArgumentException("Input data structure must be an instance of list or dict")

	if(len(struct) != 1):
		raise IllegalArgumentException("Input data structure must be one-dimensional")

	if(isinstance(struct, list)):
		return struct[0]

	return struct.popitem()[1]